package com.pingan.haofang.searchcloud.api.query.parser.visitor;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.pingan.haofang.searchcloud.api.matedata.IndexMeta;
import com.pingan.haofang.searchcloud.api.matedata.constant.SQLConstant;
import com.pingan.haofang.searchcloud.api.query.parser.constant.SqlVisitorStep;
import com.pingan.haofang.searchcloud.api.query.parser.context.SqlExecutionContext;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.Condition;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.ConditionGroup;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.Operator;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.SqlNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.ColumnNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.TextNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.util.SqlNodeUtil;
import com.pingan.haofang.searchcloud.api.query.parser.element.QueryBlock;
import com.pingan.haofang.searchcloud.api.query.parser.exception.SqlParseException;

import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.arithmetic.Concat;
import net.sf.jsqlparser.expression.operators.arithmetic.Modulo;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.Between;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.GreaterThan;
import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.expression.operators.relational.MinorThan;
import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals;
import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
import net.sf.jsqlparser.expression.operators.relational.RegExpMatchOperator;
import net.sf.jsqlparser.expression.operators.relational.RegExpMySQLOperator;
import net.sf.jsqlparser.statement.select.SubSelect;

/**
 * ClassName: ConditionVisitor <br>
 * Function: where条件访问器 where user_id=100 and ...<br>
 * eg:
 *
 * @author luyi
 * @version 1.0.0
 * @since 1.0.0
 * @date 20150523
 */
public class ConditionVisitor extends AbstractExpressionVisitor {

    /**
     * 条件索引，以and、or分割，从0开始计数
     */
    private int currentConditionIndex;

    /**
     * 访问器当前步骤
     */
    private SqlVisitorStep step;

    /**
     * WHERE条件集合
     */
    private List<ConditionGroup> whereConditions;

    /**
     * SQL执行上下文
     */
    private SqlExecutionContext context;

    /**
     * 当前QUERYBLOCK
     */
    private QueryBlock current;

    /**
     * 当前where条件
     */
    private ConditionGroup currentWhereConditionGroup;

    /**
     * 是否在括号后面
     */
    private boolean afterParenthesis;

    /**
     * OR出现索引
     */
    private Set<Integer> orIndexSet;

    /**
     * 索引元数据
     */
    private IndexMeta meta;

    public ConditionVisitor(SqlVisitorStep step, QueryBlock current, SqlExecutionContext context, IndexMeta meta) {
        this.step = step;
        this.context = context;
        this.current = current;
        this.meta = meta;
        this.whereConditions = new ArrayList<ConditionGroup>();
        currentWhereConditionGroup = new ConditionGroup();
        orIndexSet = new HashSet<Integer>(5);
    }

    public ConditionVisitor(SqlVisitorStep step, QueryBlock current, SqlExecutionContext context, IndexMeta meta,
            int conditionIndex) {
        this(step, current, context, meta);
        currentConditionIndex = conditionIndex;
    }

    @Override
    public void endVisit() {
        if (!currentWhereConditionGroup.isEmpty()) {
            whereConditions.add(currentWhereConditionGroup);
        }
    }

    /**
     * AND表达式解析
     */
    @Override
    public void visit(AndExpression andExpression) {
        andExpression.getLeftExpression().accept(this);
        if (afterParenthesis) {
            if (!currentWhereConditionGroup.isEmpty()) {
                whereConditions.add(currentWhereConditionGroup);
            }
            currentWhereConditionGroup = new ConditionGroup();
            afterParenthesis = false;
        }
        andExpression.getRightExpression().accept(this);
    }

    /**
     * OR表达式解析
     */
    @Override
    public void visit(OrExpression orExpression) {
        orExpression.getLeftExpression().accept(this);
        if (!currentWhereConditionGroup.isEmpty()) {
            whereConditions.add(currentWhereConditionGroup);
        }
        orIndexSet.add(whereConditions.size());
        currentWhereConditionGroup = new ConditionGroup();
        orExpression.getRightExpression().accept(this);
    }

    /**
     * ()解析
     */
    @Override
    public void visit(Parenthesis parenthesis) {
        if (!currentWhereConditionGroup.isEmpty()) {
            whereConditions.add(currentWhereConditionGroup);
        }
        currentWhereConditionGroup = new ConditionGroup();
        ConditionVisitor childVisitor = new ConditionVisitor(step, current, context, meta, currentConditionIndex);
        parenthesis.getExpression().accept(childVisitor);
        childVisitor.endVisit();
        currentWhereConditionGroup.setChildList(childVisitor.getWhereConditions());

        currentWhereConditionGroup.setOrIndexSet(childVisitor.getOrIndexSet());
        currentConditionIndex = childVisitor.getCurrentConditionIndex();

        afterParenthesis = true;
    }

    /**
     * between的特殊处理<br>
     * 1. between需转化为一个整体处理，即A between a and b转化为(A between a and b)<br>
     * 2. between需拆解为两个binary condition<br>
     * 3. not between优化为A < a or A > b<br>
     */
    @Override
    public void visit(Between between) {
        if (currentWhereConditionGroup != null && !currentWhereConditionGroup.isEmpty()) {
            whereConditions.add(currentWhereConditionGroup);
        }
        currentWhereConditionGroup = new ConditionGroup();

        List<ConditionGroup> whereConditions = new ArrayList<ConditionGroup>();
        ConditionGroup whereConditionGroup = new ConditionGroup();
        whereConditions.add(whereConditionGroup);

        ConditionNodeVisitor left = new ConditionNodeVisitor(step, meta);
        between.getLeftExpression().accept(left);

        if (!SqlNodeUtil.isColumn(left.getConditionNode())) {
            throw new SqlParseException("");
        }

        ConditionNodeVisitor betweenStart = new ConditionNodeVisitor(step, meta);
        between.getBetweenExpressionStart().accept(betweenStart);

        ConditionNodeVisitor betweenEnd = new ConditionNodeVisitor(step, meta);
        between.getBetweenExpressionEnd().accept(betweenEnd);
        
        if (between.isNot()) {
            throw new SqlParseException("not between is not support");
        } else {
            Condition leftCondition = new Condition(currentConditionIndex, null);
            leftCondition.setLeft((ColumnNode) left.getConditionNode());
            leftCondition.setOperator(Operator.GE);
            leftCondition.setRight(betweenStart.getConditionNode());

            Condition rightCondition = new Condition(currentConditionIndex, null);
            rightCondition.setLeft((ColumnNode) left.getConditionNode());
            rightCondition.setOperator(Operator.LE);
            rightCondition.setRight(betweenEnd.getConditionNode());

            whereConditionGroup.addCondition(leftCondition);
            whereConditionGroup.addCondition(rightCondition);
        }

        currentWhereConditionGroup.setChildList(whereConditions);
        currentWhereConditionGroup.setOrIndexSet(new HashSet<Integer>(0));
        ++currentConditionIndex;
        afterParenthesis = true;
    }

    @Override
    public void visit(EqualsTo equalsTo) {
        visitBinaryExpression(equalsTo);
    }

    @Override
    public void visit(GreaterThan greaterThan) {
        visitBinaryExpression(greaterThan);
    }

    @Override
    public void visit(GreaterThanEquals greaterThanEquals) {
        visitBinaryExpression(greaterThanEquals);
    }

    /**
     * IN(...)解析
     */
    @Override
    public void visit(InExpression inExpression) {
        ConditionNodeVisitor left = new ConditionNodeVisitor(step, meta);
        inExpression.getLeftExpression().accept(left);
        
        ItemsList rightItemList = inExpression.getRightItemsList();
        // 仅支持in(item1, item2, item3)
        if (rightItemList instanceof SubSelect) {
            throw new SqlParseException("in expression can only support in(item1,item2,item3)");
        } else if (rightItemList instanceof ExpressionList) {
            ExpressionList exprList = (ExpressionList) rightItemList;

            List<SqlNode> conditionNodeList = new ArrayList<SqlNode>(exprList.getExpressions().size());
            for (Expression expr : exprList.getExpressions()) {
                ConditionNodeVisitor right = new ConditionNodeVisitor(step, meta);
                expr.accept(right);
                conditionNodeList.add(right.getConditionNode());
            }

            Condition condition = new Condition(currentConditionIndex, inExpression.toString());
            if (inExpression.isNot()) {
                condition.setOperator(Operator.NE);
            } else {
                condition.setOperator(Operator.EQ);
            }

            if (!SqlNodeUtil.isColumn(left.getConditionNode())) {
                throw new SqlParseException("");
            }
            condition.setLeft((ColumnNode) left.getConditionNode());
            condition.setRightList(conditionNodeList);

            currentWhereConditionGroup.addCondition(condition);
        }
        ++currentConditionIndex;
    }

    @Override
    public void visit(IsNullExpression isNullExpression) {
        ConditionNodeVisitor left = new ConditionNodeVisitor(step, meta);
        isNullExpression.getLeftExpression().accept(left);

        Condition condition = new Condition(currentConditionIndex, isNullExpression.toString());
        condition.setOperator(isNullExpression.isNot() ? Operator.IS_NOT : Operator.IS);
        if (!SqlNodeUtil.isColumn(left.getConditionNode())) {
            throw new SqlParseException("");
        }
        condition.setLeft((ColumnNode) left.getConditionNode());
        condition.setRight(new TextNode(null));
        currentWhereConditionGroup.addCondition(condition);
        ++currentConditionIndex;
    }

    @Override
    public void visit(LikeExpression likeExpression) {
        visitBinaryExpression(likeExpression);
    }

    @Override
    public void visit(MinorThan minorThan) {
        visitBinaryExpression(minorThan);
    }

    @Override
    public void visit(MinorThanEquals minorThanEquals) {
        visitBinaryExpression(minorThanEquals);
    }

    @Override
    public void visit(NotEqualsTo notEqualsTo) {
        visitBinaryExpression(notEqualsTo);
    }

    @Override
    public void visit(SubSelect subSelect) {
    }

    @Override
    public void visit(ExistsExpression existsExpression) {
        existsExpression.getRightExpression().accept(this);
    }

    @Override
    public void visit(Concat concat) {
        visitBinaryExpression(concat);
    }

    @Override
    public void visit(Modulo modulo) {
        visitBinaryExpression(modulo);
    }

    @Override
    public void visit(RegExpMatchOperator rexpr) {
        visitBinaryExpression(rexpr);
    }

    @Override
    public void visit(RegExpMySQLOperator regExpMySQLOperator) {
        visitBinaryExpression(regExpMySQLOperator);
    }

    /**
     * 二值条件访问<br>
     * eg: user_id = 100<br>
     * username like 'aa%'
     * 
     * @param expr
     */
    private void visitBinaryExpression(BinaryExpression expr) {
        Expression rightExpr = expr.getRightExpression();

        // 子查询的特殊处理
        if (rightExpr instanceof SubSelect) {
            rightExpr.accept(this);
        } else {
            // 解析左侧节点
            ConditionNodeVisitor left = new ConditionNodeVisitor(step, meta);
            expr.getLeftExpression().accept(left);

            // 右侧节点解析
            ConditionNodeVisitor right = new ConditionNodeVisitor(step, meta);
            rightExpr.accept(right);

            SqlNode leftNode = left.getConditionNode();
            SqlNode rightNode = right.getConditionNode();

            Condition condition = new Condition(currentConditionIndex, expr.toString());
            if (SqlNodeUtil.isColumn(leftNode) && !SqlNodeUtil.isColumn(rightNode)) {
                condition.setLeft((ColumnNode)leftNode);
                condition.setRight(rightNode);
                condition.setOperator(Operator.get(expr.getStringExpression()));
            } else if (!SqlNodeUtil.isColumn(leftNode) && SqlNodeUtil.isColumn(rightNode)) {
                condition.setLeft((ColumnNode) rightNode);
                condition.setRight(leftNode);
                condition.setOperator(Operator.getReverse(Operator.get(expr.getStringExpression())));
            } else {
                throw new SqlParseException("where condition needs one column query");
            }

            // 针对保留查询d的特殊处理
            if (step == SqlVisitorStep.WHERE && SQLConstant.GEO_D_KEY.equals(condition.getLeft().getColumnName())) {
                if(condition.getOperator() != Operator.EQ) {
                    throw new SqlParseException("GEO d query can only support equals condition");
                }

                try {
                    Double distance = Double.parseDouble(String.valueOf(condition.getRightOne().getValue()));
                    currentWhereConditionGroup.setDistance(distance);
                } catch (Exception e) {
                    throw new SqlParseException("d query is illegal, please check");
                }
            } else {
                currentWhereConditionGroup.addCondition(condition);
            }
        }

        ++currentConditionIndex;
    }

    /**
     * 得到where条件
     * 
     * @return
     */
    public List<ConditionGroup> getWhereConditions() {
        return whereConditions;
    }

    /**
     * 得到当前条件索引
     * 
     * @return
     */
    public int getCurrentConditionIndex() {
        return currentConditionIndex;
    }

    public Set<Integer> getOrIndexSet() {
        return orIndexSet;
    }

}
